#include "wifis.h"
#include "mqtts.h"
#include "assert.h"
#include "taskpool.h"
#include "main.h"
#include "assitant.h"
#include "LittleFS.h"
#define fs LittleFS

/*
sub: "lily/" dev "/cmd"
pub: "control/upload/" dev
out: "lily/" + client_id + "/msg"
*/
const char *mqtt_server = "aahqtdc.iot.gz.baidubce.com";
String client_id = dev;
String mqtt_user = "thingidp@aahqtdc|" dev;
String mqtt_pw = "a9feed2715042d94bd23ab9955772a0f";
String mqtt_pub_topic = "control/upload";

WiFiClient espClient;
PubSubClient mqtt_client(espClient);

const char *names[] = {"dev", "username", "password"};
static String *fields[] = {&client_id, &mqtt_user, &mqtt_pw};
const int fields_n = sizeof(names) / sizeof(char *);
String mqt_msg_out_topic = "lily/" + client_id + "/cmd";

unsigned long sync_start_time = 0;
unsigned long sync_end_time = 0;
time_t sys_time_base = 0;

int cmd_mqtt(int n, char *args[]);
int cmd_time(int n, char *args[]);
void *_ext_mqtt_pipe_stream(void *arg, char *data, int len);

void mqtt_setup(void (*callback)(char *topic, byte *payload, unsigned int length))
{
    mqtt_client.setServer(mqtt_server, 1883);
    mqtt_client.setCallback(callback);
    new_cmd("set", cmd_set);
    new_cmd("mqtt", cmd_mqtt);
    new_cmd("utime", cmd_time);
}

int mqtt_reconnect()
{
    var _in_ticking = stop_schedule();
    String top = "lily/" + client_id + "/msg";
    if (mqtt_client.connect(client_id.c_str(), mqtt_user.c_str(), mqtt_pw.c_str(), top.c_str(), 1, 0, "$offline"))
    {

        String topic = "lily/" + client_id + "/cmd";
        var ok = mqtt_client.subscribe(topic.c_str());
        if (!ok)
        {
            xouts("sub failed");
        }
        // publish a msg "$online" at first
        mqtt_client.publish(top.c_str(), "$online");
        if (!sys_time_base)
        {
            start_sync_time();
        }
        // mqtt_client.loop();
        // top = "sys/" + client_id + "/time";
        // mqtt_client.publish(top.c_str(), "");
        // xouts(top);
        // sync_start_time = millis(); // start measuring
        // mqtt_client.loop();
        if (_in_ticking)
            resume_schedule();
        return 0;
    }
    xout_("mqtt connect failed, rc=");
    xouts(mqtt_client.state());
    if (_in_ticking)
        resume_schedule();
    return -2;
}

int mqtt_try_connect()
{
    if (!WiFi.isConnected())
    {
        // remove_a_timer(mqtt_loop);
        return -1;
    }
    if (!mqtt_client.connected())
    {
        var ok = mqtt_reconnect();
        if (ok >= 0)
        {
            if (!had_timer(mqtt_loop))
                public_a_timer(mqtt_loop, Hz(11));
            remove_timer(mqtt_try_connect);
        }
        return -1;
    }
    if (!had_timer(mqtt_loop))
        public_a_timer(mqtt_loop, Hz(11));
    remove_timer(mqtt_try_connect);
    return 0;
}

int mqtt_loop()
{
    if (!WiFi.isConnected())
    {
        if (!had_timer(mqtt_try_connect))
            public_a_timer(mqtt_try_connect, Second(60));
        remove_a_timer(mqtt_loop);
        return -1;
    }
    if (!mqtt_client.connected())
    {
        var ok = mqtt_reconnect();
        if (!ok)
        {
            if (!had_timer(mqtt_try_connect))
                public_a_timer(mqtt_try_connect, Second(60));
            remove_timer(mqtt_loop);
        }
        return -1;
    }
    mqtt_client.loop();
    return 0;
}
// set dev=dev12
int cmd_set(int n, char *args[])
{
    if (n < 2)
        return -2;
    String a = args[1];
    int ind = a.indexOf('=');
    if (ind < 0)
        return -1;
    String name = a.substring(0, ind);
    name.trim();
    a = a.substring(ind + 1);
    a.trim();
    for (int i = 0; i < fields_n; i++)
    {
        if (name != names[i])
            continue;
        if (!a.isEmpty())
        {
            *(fields[i]) = a;
        }
        out_(name);
        out_("=");
        outs(*(fields[i]));
        break;
    }
    return 0;
}

bool test_config()
{
    File f = fs.open(config_file, "r");
    if (!f)
    {
        return false;
    }
    int score = 0;
    while (f.available())
    {
        String s = f.readStringUntil('\n');
        s.trim();
        if (s[0] != '$')
            continue;
        auto ind = s.indexOf('=');
        if (ind < 0)
            continue;
        auto a = s.substring(1, ind);
        a.trim();
        for (int i = 0; i < fields_n; i++)
        {
            if (a != names[i])
                continue;
            score |= (1 << i);
            if (score == 7)
            {
                f.close();
                return true;
            }
            break;
        }
    }
    f.close();
    return false;
}

void mqtt_out(const char *msg)
{
    if (!mqtt_client.connected())
        return;
    String top = "lily/" + client_id + "/msg";
    var ok = mqtt_client.publish(top.c_str(), msg);
    if (!ok)
    {
        mqtt_client.loop();
        mqtt_client.publish(top.c_str(), msg);
    }
}

void mqtt_out(String &msg)
{
    mqtt_out(msg.c_str());
}

int cmd_mqtt(int n, char *args[])
{
    if (n < 2)
        return -1;
    String mode(args[1]);
    if (mode == "-np")
    {
        assert_msg(n > 2, "usage:mqtt -np topic");
        int len = strlen(args[2]) + 1;
        var topic = new char[len]; // memory leak
        assert_true(topic);
        // memcpy(topic, args[2], len);
        for (int i = 0; i < len; i++)
            topic[i] = args[2][i];
        public_a_new_pipe(topic, topic, _ext_mqtt_pipe_stream, 'e');
        return 0;
    }
    if (mode == "-sub")
    {
        mqtt_client.subscribe(args[2]);
        out_("sub:");
        outs(args[2]);
        return 0;
    }
    else if (mode == "-h")
    {
        outs("mqtt [topic] msg:public topic");
        outs("  -sub topic:subscribe topic");
        outs("  -np topic: create new topic pipeline");
        outs("  -h:help");
        return 0;
    }
    if (n == 2)
    {
        mqtt_out(args[1]);
        return 0;
    }
    int i = 2;
    strcpy(tx, args[i++]);
    for (; i < n; i++)
    {
        strcat(tx, " ");
        strcat(tx, args[i]);
    }
    var ok = mqtt_client.publish(args[1], tx);
    if (!ok)
    {
        out_("failed to pub:");
    }
    out_(args[1]);
    out_(":");
    outs(tx);
    return 1;
}

int start_sync_time()
{
    if (!WiFi.isConnected() || !mqtt_client.connected())
        return -__LINE__;
    mqtt_client.loop();
    var top = "sys/" + client_id + "/time";
    sprintf(tx, "%d", __firmware_version);
    mqtt_client.publish(top.c_str(), tx);
    xouts("start sync time");
    sync_start_time = millis(); // start measuring
    mqtt_client.loop();
    Serial.print("start at ");
    Serial.print(sync_start_time);
    xouts("ms");
    return 0;
}
time_t service_time()
{
    if (!sync_end_time)
        return 0;
    var n = millis();
    var delta_ms = n - sync_end_time;
    time_t time = sys_time_base + delta_ms / 1000;
    return time;
}
int cmd_time(int n, char *args[])
{
    if (n == 1)
    {
        assert_msg(sync_end_time, "time not avaliable");
        var t = service_time();
        var now_tm = gmtime(&t);
        str_tm(tx, now_tm);
        outs(tx);
        return 0;
    }
    if (Arg(1) == "-s")
    {
        return start_sync_time();
    }
    outs("utime :see current time");
    outs("-s :sync system time");
    outs("-h :help");
    return 0;
}

void *_ext_mqtt_pipe_stream(void *arg, char *data, int len)
{
    if (!mqtt_client.connected())
        return NULL;

    var ok = mqtt_client.publish((char *)arg, data, len);
    if (!ok)
    {
        out_("failed to pub:");
    }
    return NULL;
}